Skip to content

fix(claude/oauth): fall back when five_hour window is missing from response#741

Open
Sanjays2402 wants to merge 2 commits intosteipete:mainfrom
Sanjays2402:fix/claude-oauth-missing-five-hour
Open

fix(claude/oauth): fall back when five_hour window is missing from response#741
Sanjays2402 wants to merge 2 commits intosteipete:mainfrom
Sanjays2402:fix/claude-oauth-missing-five-hour

Conversation

@Sanjays2402
Copy link
Copy Markdown

Summary

Fixes #726.

When the Claude OAuth usage response omits the five_hour block (or when
five_hour is present but has no utilization value), mapOAuthUsage
was discarding the entire snapshot with ClaudeUsageError.parseFailed("missing session data"),
even if the response still contained seven_day, seven_day_sonnet,
seven_day_opus, or seven_day_oauth_apps. The user's menu bar therefore
went blank despite the API returning usable data — matching the exact
symptom reported in the issue ("raw response returned, but parser drops
it after a 5h query returns none").

Reproduction

Before this change, on main:

let json = """
{
  "seven_day": { "utilization": 42, "resets_at": "2025-12-29T23:00:00.000Z" },
  "seven_day_sonnet": { "utilization": 17, "resets_at": "2025-12-29T23:00:00.000Z" }
}
"""
try ClaudeUsageFetcher._mapOAuthUsageForTesting(Data(json.utf8))
// throws ClaudeUsageError.parseFailed("missing session data")

After this change the same input returns a ClaudeUsageSnapshot with the
seven-day window promoted to primary (and kept as secondary) and the
Sonnet window as opus.

Approach

mapOAuthUsage now falls back through the available windows in a simple
chain of ?? expressions:

guard let primary = makeWindow(usage.fiveHour, windowMinutes: 5 * 60)
    ?? makeWindow(usage.sevenDay, windowMinutes: 7 * 24 * 60)
    ?? makeWindow(usage.sevenDaySonnet, windowMinutes: 7 * 24 * 60)
    ?? makeWindow(usage.sevenDayOpus, windowMinutes: 7 * 24 * 60)
    ?? makeWindow(usage.sevenDayOAuthApps, windowMinutes: 7 * 24 * 60)
else {
    throw ClaudeUsageError.parseFailed("missing session data")
}

secondary and opus assignments are unchanged, so in the common case
(five-hour present) the snapshot looks identical to today. The error is
only thrown when nothing at all is available.

This mirrors the tolerant decoding pattern added for Codex OAuth in #710.

Tests

Three new regression tests under Tests/CodexBarTests/ClaudeUsageTests.swift:

  1. mapOAuthUsage falls back to seven-day window when five_hour is absent
  2. mapOAuthUsage falls back when five_hour has no utilization — the exact
    shape from the reporter's debug output
  3. mapOAuthUsage throws when no windows are present — makes sure we
    don't synthesise fake data when the response is empty

Build / verification

  • swift build → clean on Swift 6.2 (Xcode 16.4, macOS 15.5)
  • swiftformat --config .swiftformat <changed files> → clean
  • Regression tests are colocated with ClaudeUsageTests and use the
    existing _mapOAuthUsageForTesting debug hook; no new debug surface
    was needed.

Note on swift test: swift test currently fails on this repo from
a clean checkout (both on main and with this change) because the
KeyboardShortcuts dependency's #Preview macros can't resolve
PreviewsMacros under plain SwiftPM. That's pre-existing and unrelated
to this fix — happy to adjust if CI has a different entry point I should
be running.

Scope

Code change is small and localized: 14 lines in ClaudeUsageFetcher.swift,
no public API or behavioural changes when five_hour is present.

…sponse

Previously `mapOAuthUsage` threw `ClaudeUsageError.parseFailed` whenever
the OAuth usage endpoint omitted the `five_hour` block, even when the
response still contained weekly or model-specific data. Users on plans
that don't surface a five-hour window \u2014 or whose accounts are in a
state where Anthropic temporarily omits it \u2014 saw the entire snapshot
dropped, with weekly and Sonnet/Opus utilisation hidden.

Fall back through the available windows (seven_day, seven_day_sonnet,
seven_day_opus, seven_day_oauth_apps) when five_hour is absent, so the
menu bar surfaces whatever the API did return. If every window is
missing we still surface a parse error.

Mirrors the tolerant decoding pattern added for Codex OAuth in steipete#710.

Fixes steipete#726.

Adds three regression tests:
  - five_hour absent, seven_day + sonnet present
  - five_hour present but without utilization
  - every window missing still throws
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3356613d04

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift
…ndow

When five_hour is missing from the OAuth response, mapOAuthUsage now
promotes a weekly window into snapshot.primary so the menu bar still
renders. UsageStore.handleSessionQuotaTransition previously treated
snapshot.primary as the session lane unconditionally, which meant the
weekly fallback could fire spurious session-depleted/restored
notifications on affected accounts.

Guard sessionQuotaWindow on the primary window's windowMinutes: anything
longer than ~6h is a weekly/model-specific fallback and must not drive
session-quota transitions. RateWindow values with no windowMinutes keep
the legacy behaviour to avoid regressions for providers that don't stamp
a duration (e.g. Copilot tests use windowMinutes: nil).

Adds two regression tests covering both directions of the guard.
@ratulsarna
Copy link
Copy Markdown
Collaborator

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b63e656a65

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

snapshot: UsageSnapshot) -> (window: RateWindow, source: SessionQuotaWindowSource)?
{
if let primary = snapshot.primary {
if let primary = snapshot.primary, Self.isSessionWindow(primary) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve session baseline when skipping non-session windows

Filtering snapshot.primary through isSessionWindow means Claude snapshots that only contain weekly fallback data now return no session window, which immediately clears lastKnownSessionRemaining in handleSessionQuotaTransition. In the real fallback scenario this can suppress a legitimate restored alert: if the user was previously depleted, one refresh with missing five_hour drops the baseline, and the next valid 5-hour snapshot is treated as first-seen data instead of a depleted→restored transition.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude: OAuth fetch fails with "missing session data" when five_hour field is absent (same fix needed as #710 for Codex)

2 participants